﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Data.Linq;
using System.Linq;

namespace DataLayer
{
    public class Repository
    {
        private static readonly Repository DistributionFacadeInstance = new Repository();

        private readonly CorporativeSystemDataContext _dataContext;

        public IList<Order> GetOrdersByDate(DateTime date)
        {
            return _dataContext.Orders.Where(item => item.Date.ExecutionDate.Value.Date == date.Date).ToList();
        } 

        public static Repository Instance
        {
            get { return DistributionFacadeInstance; }
        }

        private Repository()
        {
            _dataContext = new CorporativeSystemDataContext();
        }

        public void DeleteTable()
        {
            _dataContext.ProductOrders.DeleteAllOnSubmit(_dataContext.ProductOrders);
            _dataContext.Orders.DeleteAllOnSubmit(_dataContext.Orders);
            _dataContext.Dates.DeleteAllOnSubmit(_dataContext.Dates);
            _dataContext.MaterialOrderDetails.DeleteAllOnSubmit(_dataContext.MaterialOrderDetails);
            _dataContext.MaterialOrders.DeleteAllOnSubmit(_dataContext.MaterialOrders);
            _dataContext.Clients.DeleteAllOnSubmit(_dataContext.Clients);
            _dataContext.SubmitChanges();
        }

        public void UpdateMaterials(int wood = 0, int steel = 0, int glass = 0, int plastic = 0, int paper = 0, int buk = 0)
        {
            _dataContext.Materials.Single(item => item.MaterialId == 1).QuantityInStock = wood;
            _dataContext.Materials.Single(item => item.MaterialId == 2).QuantityInStock = steel;
            _dataContext.Materials.Single(item => item.MaterialId == 3).QuantityInStock = glass;
            _dataContext.Materials.Single(item => item.MaterialId == 4).QuantityInStock = plastic;
            _dataContext.Materials.Single(item => item.MaterialId == 5).QuantityInStock = paper;
            _dataContext.Materials.Single(item => item.MaterialId == 6).QuantityInStock = buk;
            _dataContext.SubmitChanges();
        }

        public IList<Order> GetOrderForLastWeek(int day)
        {
            return _dataContext.Orders.Where(item => item.Date.ExecutionDate < DateTime.Now.AddDays(day)
                                                     && item.Date.ExecutionDate > DateTime.Now.AddDays(day - 7)).ToList();
        } 

        public void DeleteAllProductsOrder(Order order)
        {
            _dataContext.ProductOrders.DeleteAllOnSubmit(order.ProductOrders);
            //_dataContext.SubmitChanges();
        }

        public void InsertProductOrders(IEnumerable<ProductOrder> orders)
        {
            _dataContext.ProductOrders.InsertAllOnSubmit(orders);
        }

        public int GetCountOfCompletedOrders(DateTime date)
        {
            return _dataContext.Orders.Where(x => x.Date.CompleteDate.Value.Date == date.Date).Count();
        }

        public int GetCountOfDelayedOrders(DateTime date)
        {
            return _dataContext.Orders.Where(x => x.Date.PredictedDate.Value.Date == date.Date && x.Date.CompleteDate.Value.Date > x.Date.PredictedDate.Value.Date).Count();
        }

        public IList<Material> GetAllMaterials()
        {
            return _dataContext.Materials.ToList();
        }
        
        public IEnumerable<Product> GetProductsByOrder(Order order)
        {
            Dictionary<int, int> productIdToCount = order.ProductOrders
                .ToDictionary(prOrder => prOrder.ProductId, prOrder => prOrder.Quantity);
            var products = new List<Product>();

            foreach (KeyValuePair<int, int> pair in productIdToCount)
            {
                int count = pair.Value;
                while (count > 0)
                {
                    products.Add(GetProductById(pair.Key));
                    count--;
                }
            }

            return products;
        }

        public Order InsertOrder(Order order)
        {
            _dataContext.Orders.InsertOnSubmit(order);
            //_dataContext.SubmitChanges();
            return order;
        }

        public IList<Order> GetAllOrders()
        {
            return _dataContext.Orders.ToList();
        } 

        public IList<Product> GetAllProducts()
        {
            return _dataContext.Products.ToList();
        } 

        public Product GetProductById(int id)
        {
            return _dataContext.Products.First(x => x.ProductId == id);
        }

        public Material GetMaterialById(int id)
        {
            return _dataContext.Materials.First(x => x.MaterialId == id);
        }

        public void DeleteOrder(Order order)
        {
            DeleteAllProductsOrder(order);

            Date date = _dataContext.Dates.SingleOrDefault(item => item.DateId == order.OrderId);
            _dataContext.Orders.DeleteOnSubmit(order);
            if (date != null)
                _dataContext.Dates.DeleteOnSubmit(date);
            //_dataContext.SubmitChanges();
        }

        public void InsertMaterialOrder(MaterialOrder materialOrder)
        {
            _dataContext.MaterialOrders.InsertOnSubmit(materialOrder);
            //_dataContext.SubmitChanges();
        }

        public MaterialOrder GetTodayDelivery(DateTime date)
        {
            return _dataContext.MaterialOrders
                .Where(item => item.ArrivalDate.Date == date.Date 
                    && item.Status == 1).FirstOrDefault();
        }
        
        public Dictionary<int, int> GetCountMaterialsForOrder(Order order)
        {
            IEnumerable<ProductMaterial> productMaterials = GetProductMaterialsForOrder(order);
            var materialToTotalAmount = new Dictionary<int, int>();
            foreach (var item in productMaterials)
            {
                if (!materialToTotalAmount.ContainsKey(item.MaterialId))
                    materialToTotalAmount.Add(item.MaterialId, 0);
                materialToTotalAmount[item.MaterialId] += item.Quantity;
            }
            return materialToTotalAmount;
        }

        public IEnumerable<Material> GetMaterialsForMaterialOrder(MaterialOrder materialOrder)
        {
            return from m in _dataContext.Materials
                   from mod in _dataContext.MaterialOrderDetails
                   where mod.OrderId == materialOrder.Id
                   select m;
        } 

        public IEnumerable<ProductMaterial> GetProductMaterialsForOrder(Order order)
        {
            var products = new List<ProductMaterial>();
            foreach (Product item in GetProductsByOrder(order))
            {
                products.AddRange(item.ProductMaterials);
            }
            return products;
        } 

        public void SubmitChanges()
        {
            _dataContext.SubmitChanges();
        }

        public int GetQuantityOfMaterialInStock(int materialId)
        {
            return GetMaterialById(materialId).QuantityInStock;
        }

        public MaterialOrder GetLastMaterialOrder()
        {
            return (MaterialOrder) _dataContext.MaterialOrders.Select(o => o).OrderByDescending(o => o.ArrivalDate).Take(1);
        }

        public List<MaterialOrder> GetLastTwoWeeksOrders()
        {
            var result = _dataContext.MaterialOrders.Select(o => o).OrderByDescending(o => o.ArrivalDate);
            return result.Count() > 0 ? result.Take(2).ToList() : new List<MaterialOrder>();
        }

        public void InsertDeliveryToStock(MaterialOrder materialOrder)
        {
            IList<Material> availableMaterials = _dataContext.Materials.ToList();
            foreach (MaterialOrderDetail detail in materialOrder.MaterialOrderDetails)
            {
                if (availableMaterials.Any(item => item.MaterialId == detail.MaterialId))
                    availableMaterials.Single(item => item.MaterialId == detail.MaterialId).QuantityInStock += detail.Quantity;
                else
                    throw new ArgumentException("Записи о материале нету в базе.");
            }
            
            _dataContext.SubmitChanges();
        }

        public void SubstructMaterials(Order order)
        {
            var materialToCount = GetCountMaterialsForOrder(order);
            IList<Material> allMaterials = GetAllMaterials();

            foreach (KeyValuePair<int, int> pair in materialToCount)
            {
                allMaterials.Single(item => item.MaterialId == pair.Key).QuantityInStock -= pair.Value;

                if (allMaterials.Single(item => item.MaterialId == pair.Key).QuantityInStock < 0)
                    throw new ArgumentException("Отрицательное число после производства заказа -> алгоритм проверки на доступность материалов для заказа работает неверно.");
            }

            _dataContext.SubmitChanges();
        }

        public Dictionary<int, int> GetAllMaterialOrders()
        {
            var matterials = from po in _dataContext.ProductOrders
                             join p in _dataContext.Products on po.ProductId equals p.ProductId
                             join pm in _dataContext.ProductMaterials on po.ProductId equals pm.ProductId
                             join m in _dataContext.Materials on pm.MaterialId equals m.MaterialId
                             group m by m.MaterialId into g
                             select new { MaterialId = g.Key, Quantity = g.Sum(pm => pm.ProductMaterials.Take(1).Sum(item => item.Quantity)) };
                             //select new { Name = g.Key, Quantity = g.Sum(pm => pm.ProductMaterials.Sum(item => item.Quantity) / pm.ProductMaterials.Count) };                                                          

            Dictionary<int, int> factor = new Dictionary<int, int>();

            foreach (var matterial in matterials)
            {
                factor.Add(matterial.MaterialId, matterial.Quantity);                
            }

            return factor; 
        }

        public Dictionary<int, int> GetLastWeekMaterialsInStock(int day)
        {
            var matterials = from mo in _dataContext.MaterialOrders
                             join m in _dataContext.Materials on mo.Id equals m.MaterialId
                             where mo.ArrivalDate < DateTime.Now.AddDays(day - 3) && mo.ArrivalDate > DateTime.Now.AddDays(day - 7)
                             select new { MaterialId = m.MaterialId, Quantity = m.QuantityInStock};

            Dictionary<int, int> materialsInStock = new Dictionary<int, int>();

            //string s = "";

            foreach (var matterial in matterials)
            {
                //s += matterial.MaterialId + " : " + matterial.Quantity + "\n";
                materialsInStock.Add(matterial.MaterialId, matterial.Quantity);
            }

            return materialsInStock; 
        }

        public void UpdateOrder(Order order)
        {
            Repository.Instance.SubmitChanges();
        }
    }
}
